package io.sundial.engine.impl;

import io.sundial.core.context.Context;
import io.sundial.core.lifecycle.Lifecycle;
import io.sundial.core.lifecycle.exception.DestroyingException;
import io.sundial.core.lifecycle.exception.InitializingException;
import io.sundial.protocol.Protocol;
import io.sundial.protocol.exception.MarshallingException;
import io.sundial.protocol.exception.UnmarshallingException;
import io.sundial.util.ResKit;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 协议化引擎
 *
 * @author Payne 646742615@qq.com
 * 2018/12/24 19:51
 */
public abstract class ProtocolEngine extends LoggingEngine {
    private Protocol defaultProtocol;
    private Map<String, Protocol> supportProtocols;

    @Override
    protected void initializing(Context context) throws InitializingException {
        super.initializing(context);

        // 如果没有设置就从上下文中找到所有的。
        if (supportProtocols == null || supportProtocols.isEmpty()) {
            Map<String, Protocol> map = context.fetch(Protocol.class);
            Collection<Protocol> collection = map.values();
            Map<String, Protocol> protocols = new LinkedHashMap<>();
            for (Protocol protocol : collection) {
                String name = protocol.getName();
                if (name == null || name.trim().isEmpty()) {
                    throw new InitializingException("protocol " + protocol.getClass() + " must declare a not null or blank name");
                }
                String version = protocol.getVersion();
                if (version == null || version.trim().isEmpty()) {
                    throw new InitializingException("protocol " + protocol.getClass() + " must declare a not null or blank version");
                }
                protocols.put(name.trim().toUpperCase() + "/" + version.trim().toUpperCase(), protocol);
            }
            supportProtocols = Collections.unmodifiableMap(protocols);
        }

        // 如果没配置缺省协议，就选择第一个作为默认的协议。
        if (defaultProtocol == null) {
            if (supportProtocols.isEmpty()) {
                throw new InitializingException("please config at least one protocol");
            } else {
                defaultProtocol = supportProtocols.values().iterator().next();
            }
        }

        for (Protocol supportProtocol : supportProtocols.values()) {
            if (supportProtocol instanceof Lifecycle) {
                ((Lifecycle) supportProtocol).initialize(context);
            }
        }
        if (defaultProtocol instanceof Lifecycle) {
            ((Lifecycle) defaultProtocol).initialize(context);
        }
    }

    @Override
    protected void destroying() throws DestroyingException {
        super.destroying();

        for (Protocol supportProtocol : supportProtocols.values()) {
            if (supportProtocol instanceof Lifecycle) {
                ((Lifecycle) supportProtocol).destroy();
            }
        }
        if (defaultProtocol instanceof Lifecycle) {
            ((Lifecycle) defaultProtocol).destroy();
        }

        supportProtocols = null;

        defaultProtocol = null;
    }

    protected byte[] marshall(Object obj) throws MarshallingException {
        return defaultProtocol.marshall(obj);
    }

    protected <T> T unmarshal(byte[] data, Class<T> type) throws UnmarshallingException {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
            String head = ResKit.readln(bis);
            byte[] body = new byte[bis.available()];
            //noinspection ResultOfMethodCallIgnored
            bis.read(body);
            Protocol protocol = head == null ? null : supportProtocols.get(head.toUpperCase());
            return protocol != null ? protocol.unmarshal(body, type) : defaultProtocol.unmarshal(body, type);
        } catch (IOException e) {
            throw new UnmarshallingException(e);
        }
    }

    public Protocol getDefaultProtocol() {
        return defaultProtocol;
    }

    public void setDefaultProtocol(Protocol defaultProtocol) {
        this.defaultProtocol = defaultProtocol;
    }

    public Map<String, Protocol> getSupportProtocols() {
        return supportProtocols;
    }

    public void setSupportProtocols(Map<String, Protocol> supportProtocols) {
        this.supportProtocols = supportProtocols;
    }
}
